﻿using System.Text;
using CppAst;

namespace ast;

public class GoWrapperGenerator
{
    public static string GetGoWrapperFunctionSignature(FunctionInfo functionInfo, ModuleInfo moduleInfo, int funcIndex)
    {
        var goFunctionName = GoTypeUtil.GetGoFunctionName(functionInfo, funcIndex);
        var isConstructor = functionInfo.Function.IsConstructor;


        var functionInfoExportParameters = functionInfo.ExportParameters;
        var functionReturnType = functionInfo.ExportReturnType.Type;
        var builder = new StringBuilder();
        builder.Append($"{goFunctionName}(");
        for (var index = 0; index < functionInfoExportParameters.Count; index++)
        {
            var exportParameter = functionInfoExportParameters[index];
            var exportParameterType = exportParameter.Type;
            if (index != 0)
            {
                builder.Append(", ");
            }

            var goTypeString = GoWrapperInTypeString(exportParameterType);
            builder.AppendFormat("{0} {1}", exportParameter.Name,
                goTypeString);
        }

        builder.Append(") ");
        if (!ExportConfig.FunctionReturnVoid(functionReturnType))
        {
            if (functionInfo.Function.IsNewArray)
            {
                var resultType = functionInfo.Function.ReturnType as CppPointerType;
                var goClassName = GoTypeUtil.GetGoClassName(resultType.ElementType as CppClass);
                builder.Append($"cgo.GoManagedPtr[cgo.Array[{goClassName}]]");
                builder.Append(" ");
            }
            else if (functionInfo.Function.IsIndexArray)
            {
                builder.Append($"uintptr");
                builder.Append(" ");
            }
            else
            {
                var goTypeString = GoWrapperOutTypeString(functionReturnType, isConstructor);
                builder.Append(goTypeString);
                builder.Append(" ");
            }
        }

        return builder.ToString();
    }

    public static string GetGoWrapperFunctionBody(ModuleInfo moduleInfo, string moduleName, ClassInfo? classInfo,
        FunctionInfo functionInfo,
        int index)
    {
        var isConstructor = functionInfo.Function.IsConstructor;
        if (isConstructor)
        {
            classInfo = null;
        }

        var cFunctionName =
            CWrapperGenerator.GetCppWrapperFunctionName(moduleInfo, moduleName, classInfo, functionInfo, index);


        if (functionInfo.Function.IsNewArray)
        {
            var resultType = functionInfo.Function.ReturnType as CppPointerType;
            var goClassName = GoTypeUtil.GetGoClassName(resultType.ElementType as CppClass);
            return
                $"\twrap_out := C.{cFunctionName}(C.long(n))\n\tgo_out := cgo.MakePtr[cgo.GoManagedPtr[cgo.Array[{goClassName}]]](uintptr(wrap_out))\n\treturn go_out\n";
        }

        if (functionInfo.Function.IsDeleteArray)
        {
            return $"\tvar wrap_this unsafe.Pointer\n\twrap_this = unsafe.Pointer(p)\n\tC.{cFunctionName}(wrap_this)\n";
        }

        if (functionInfo.Function.IsIndexArray)
        {
            return
                $"\tvar wrap_this unsafe.Pointer\n\twrap_this = unsafe.Pointer(p)\n\twrap_out := C.{cFunctionName}(wrap_this, C.long(n))\n\tgo_out := uintptr(wrap_out)\n\treturn go_out\n";
        }


        var functionInfoExportParameters = functionInfo.ExportParameters;
        var functionReturnType = functionInfo.ExportReturnType.Type;
        var hasThis = classInfo != null;


        //变量声明
        var builder = new StringBuilder();
        if (classInfo != null) builder.AppendFormat("\tvar wrap_this {0}\n", GoCallCInTypeString(classInfo!.ClassType));

        for (var i = 0; i < functionInfoExportParameters.Count; i++)
        {
            var para = functionInfoExportParameters[i];
            var exportParameterType = para.Type;

            builder.AppendFormat("\tvar wrap_arg_{0} {1}\n", para.Name,
                GoCallCInTypeString(exportParameterType));
        }

        var hasReturnResult = !ExportConfig.FunctionReturnVoid(functionReturnType);
        if (hasReturnResult)
        {
            // builder.AppendFormat("\tvar wrap_out {0}\n", GoCallCOutTypeString(functionReturnType));
            // builder.AppendFormat("\tvar go_out {0}\n", GoWrapperOutTypeString(functionReturnType, isConstructor));
        }

        //进行go向wrap 变量赋值
        if (hasThis) builder.Append("\twrap_this = unsafe.Pointer(p)\n");

        for (var i = 0; i < functionInfoExportParameters.Count; i++)
        {
            var exportParameter = functionInfoExportParameters[i];

            if (exportParameter.Type is CppPrimitiveType || exportParameter.Type is CppEnum)
            {
                builder.AppendFormat("\twrap_arg_{0} = ({1})({2})\n", exportParameter.Name,
                    GoCallCInTypeString(exportParameter.Type),
                    exportParameter.Name);
            }
            else
            {
                var goInType = GoWrapperInTypeString(exportParameter.Type);
                if (goInType.StartsWith("*"))
                {
                    builder.AppendFormat("\twrap_arg_{0} = ({1})({2})\n", exportParameter.Name,
                        GoCallCInTypeString(exportParameter.Type), exportParameter.Name);
                }
                else if (exportParameter.Type is CppArrayType cppArrayType)
                {
                    if (cppArrayType.ElementType is CppClass)
                    {
                        builder.Append(
                            $"\twrap_arg_{exportParameter.Name} = ({GoCallCInTypeString(exportParameter.Type)})({exportParameter.Name})\n");
                    }
                    else if (cppArrayType.Size == -1)
                    {
                        // int x[] =>int*
                        builder.Append(
                            $"\twrap_arg_{exportParameter.Name} = ({GoCallCInTypeString(exportParameter.Type)})({exportParameter.Name})\n");
                    }
                    else
                    {
                        // int x[3]  =>*int[3]
                        builder.Append(
                            $"\twrap_arg_{exportParameter.Name} = ({GoCallCInTypeString(exportParameter.Type)})(&{exportParameter.Name}[0])\n");
                    }
                }
                else if (goInType == "uintptr")
                {
                    builder.AppendFormat("\twrap_arg_{0} = ({1})({2})\n", exportParameter.Name,
                        GoCallCInTypeString(exportParameter.Type), exportParameter.Name);
                }
                else
                {
                    builder.AppendFormat("\twrap_arg_{0} = ({1})({2})\n", exportParameter.Name,
                        GoCallCInTypeString(exportParameter.Type), exportParameter.Name);
                }
            }
        }


        if (hasReturnResult)
            builder.Append("\twrap_out := ");
        else
            builder.Append("\t");


        if (hasThis)
            builder.AppendFormat("C.{0}(wrap_this", cFunctionName);
        else
            builder.AppendFormat("C.{0}(", cFunctionName);

        for (var i = 0; i < functionInfoExportParameters.Count; i++)
        {
            if (i != 0 || hasThis)
            {
                builder.Append(", ");
            }

            var para = functionInfoExportParameters[i];
            builder.Append($"wrap_arg_{para.Name}");
        }

        builder.Append(")\n");
        if (hasReturnResult)
        {
            builder.AppendFormat("\tgo_out := {0}\n", WrapOutToGoOut(functionReturnType!, isConstructor));
            builder.Append("\treturn go_out\n");
        }

        return builder.ToString();
    }


    public static string GoWrapperOutTypeString(CppType cppType, bool isConstructor)
    {
        if (isConstructor) return "cgo.GoManagedPtr[" + GoTypeUtil.GetGoTypeString(cppType) + "]";
        if (cppType is CppClass)
        {
            return "cgo.GoManagedPtr[" + GoTypeUtil.GetGoTypeString(cppType) + "]";
        }

        return GoTypeUtil.GetGoTypeString(cppType);
    }

    public static string GoWrapperInTypeString(CppType exportParameterType)
    {
        var goTypeString = GoTypeUtil.GetGoTypeString(exportParameterType);
        return goTypeString;
    }


    public static string GoCallCInTypeString(CppType exportParameterType)
    {
        switch (exportParameterType)
        {
            case CppPrimitiveType cppPrimitiveType:
                return "C." + CPrimitiveToCgoTypeName(cppPrimitiveType.Kind);
            case CppEnum cppEnum:
                return GoCallCInTypeString(cppEnum.IntegerType);
            default:
                return "unsafe.Pointer";
        }
    }

    public static string CPrimitiveToCgoTypeName(CppPrimitiveKind cppPrimitiveKind)
    {
        switch (cppPrimitiveKind)
        {
            case CppPrimitiveKind.Void:
                return "";
            case CppPrimitiveKind.Bool:
                return "_Bool";
            case CppPrimitiveKind.WChar:
                return "short";
            case CppPrimitiveKind.Char:
                return "char";
            case CppPrimitiveKind.UnsignedChar:
                return "uchar";
            case CppPrimitiveKind.SignedChar:
                return "schar";
            case CppPrimitiveKind.Short:
                return "short";
            case CppPrimitiveKind.Int:
                return "int";
            case CppPrimitiveKind.Long:
                return "long";
            case CppPrimitiveKind.LongLong:
                return "longlong";

            case CppPrimitiveKind.UnsignedShort:
                return "ushort";
            case CppPrimitiveKind.UnsignedInt:
                return "uint";

            case CppPrimitiveKind.UnsignedLongLong:
                return "ulonglong";
            case CppPrimitiveKind.Float:
                return "float";
            case CppPrimitiveKind.Double:
                return "double";
            case CppPrimitiveKind.LongDouble:
                return "double";
        }

        throw new Exception("not implement");
    }


    public static string WrapOutToGoOut(CppType returnType, bool isConstructor)
    {
        var outTypeString = GoWrapperOutTypeString(returnType, isConstructor);
        if (outTypeString.StartsWith("*")) return $"({outTypeString})(unsafe.Pointer(wrap_out))";
        if (outTypeString.Equals("uintptr")) return $"({outTypeString})(unsafe.Pointer(wrap_out))";
        if (returnType is CppArrayType cppArrayType)
        {
            if (cppArrayType.Size == -1)
            {
                return $"(*{GoTypeUtil.GetGoTypeString(cppArrayType.ElementType)})((unsafe.Pointer)(wrap_out))";
            }
            else
            {
                return
                    $"(*[{cppArrayType.Size}]{GoTypeUtil.GetGoTypeString(cppArrayType.ElementType)})((unsafe.Pointer)(wrap_out))";
            }
        }

        if (returnType is CppPrimitiveType cppPrimitiveType) return $"{outTypeString}(wrap_out)";
        if (returnType is CppEnum) return $"{outTypeString}(wrap_out)";

        return $"cgo.MakePtr[{outTypeString}](uintptr(wrap_out))";
    }


    public static string GetCgoExternFunctionSignature(ModuleInfo moduleInfo, string moduleName, ClassInfo? classInfo,
        FunctionInfo functionInfo, int funIndex)
    {
        var isConstructor = functionInfo.Function.IsConstructor;
        var builder = new StringBuilder();
        if (isConstructor) classInfo = null;

        var classTypeName = classInfo == null ? "" : classInfo.ClassType.Name;
        var functionReturnType = functionInfo.ExportReturnType.Type;
        if (!ExportConfig.FunctionReturnVoid(functionReturnType))
        {
            var cppWrapOutTypeName = CgoExternOutType(functionReturnType, isConstructor);
            builder.Append(cppWrapOutTypeName + " ");
        }
        else
        {
            builder.Append("void ");
        }

        builder.Append(
            $"{CWrapperGenerator.GetCppWrapperFunctionName(moduleInfo, moduleName, classInfo, functionInfo, funIndex)}(");
        var isFirstArg = true;
        if (classInfo != null)
        {
            isFirstArg = false;
            builder.Append($"{CgoExternInType(classInfo.ClassType)} c_this");
        }

        for (var index = 0; index < functionInfo.ExportParameters.Count; index++)
        {
            var parameter = functionInfo.ExportParameters[index];
            if (!isFirstArg)
                builder.Append(",");
            else
                isFirstArg = false;

            builder.Append(
                $"{CgoExternInType(parameter.Type)} {parameter.Name}");
        }

        builder.Append(")");


        return builder.ToString();
    }

    private static string CgoExternInType(CppType cppType)
    {
        switch (cppType)
        {
            case CppPrimitiveType cppPrimitiveType:
                return cppPrimitiveType.ToString();
            case CppEnum cppEnumType:
                return CgoExternInType(cppEnumType.IntegerType);
            default:
                return "void *";
        }
    }

    private static string CgoExternOutType(CppType cppType, bool isConstructor)
    {
        if (isConstructor)
        {
            return "void*";
        }

        return CgoExternInType(cppType);
    }
}